// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
// Copyright (c) 2019 Gui.H
// 修改 tozhang: 使用excelconfig 作为配置来源

using System;
using System.Buffers;
using System.Net.NetworkInformation;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using hub.Core.Extensions;
using hub.Core.Handlers.Client;
using hub.Core.Models;
using hub.Core.Models.Massage;
using hub.core.Utilitys;
using Microsoft.Extensions.Logging;
using ExcelConfig;
using hub.data;
using System.Collections.Generic;
using FastTunnel.Core.Handlers.Client;
using SanlieHash.GuoMi;
using SanlieHash;
using System.Linq;
using System.Text.Json.Serialization;

namespace hub.Core.Client
{
    [JsonSourceGenerationOptions(WriteIndented = false)]
    [JsonSerializable(typeof(LogInMassage))]
    [JsonSerializable(typeof(Message<TunnelMassage>))]
    public partial class SourceGenerationContext : JsonSerializerContext
    {
    }

    public class FastTunnelClient : IFastTunnelClient
    {
        private ClientWebSocket socket;

        public readonly ILogger<FastTunnelClient> _logger;

        private readonly SwapHandler swapHandler;
        private readonly LogHandler logHandler;

        private static ReadOnlySpan<byte> EndSpan => new ReadOnlySpan<byte>(new byte[] { (byte)'\n' });

        /// <summary>
        /// 重新连接准备工作
        /// </summary>
        public static event EventHandler ReConnectFunction;
        public FastTunnelClient(
            ILogger<FastTunnelClient> logger,
            LogHandler logHandler,
            SwapHandler newCustomerHandler)
        {
            _logger = logger;
            this.logHandler = logHandler;
            swapHandler = newCustomerHandler;
        }

        /// <summary>
        /// 启动客户端
        /// </summary>
        public virtual async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("===== FastTunnel Client Start =====");

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    if (ReConnectFunction != null) ReConnectFunction.Invoke(this, EventArgs.Empty);
                    await loginAsync(cancellationToken);
                    await ReceiveServerAsync(cancellationToken);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex.Message);
                }

                await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
            }

            _logger.LogInformation("===== FastTunnel Client End =====");
        }

        private async Task loginAsync(CancellationToken cancellationToken)
        {
            var logMsg = GetLoginMsg(cancellationToken);
            if (socket != null)
            {
                socket.Abort();
            }

            // 连接到的目标IP
            socket = new ClientWebSocket();
            socket.Options.RemoteCertificateValidationCallback = delegate { return true; };
            socket.Options.SetRequestHeader(FastTunnelConst.FASTTUNNEL_VERSION, AssemblyUtility.GetVersion()?.ToString());
            socket.Options.SetRequestHeader(FastTunnelConst.FASTTUNNEL_TOKEN, HUB.config.Text("令牌"));
            var sm3 = new SM3();
            var ticks = DateTime.UtcNow.Ticks+string.Empty;

            socket.Options.SetRequestHeader(FastTunnelConst.HUB_UserName, sm3.Make(System.Text.UTF8Encoding.UTF8.GetBytes(HUB.config.Text("Account.Name")+ ticks)));
            socket.Options.SetRequestHeader(FastTunnelConst.HUB_UserPass, FastHash.GetSha3_512(ticks + HUB.config.Text("Account.Password")));
            socket.Options.SetRequestHeader(FastTunnelConst.HUB_TICKS, ticks);

            string address = HUB.config.Text("hub地址"); // 你可以替换为任何你想 ping 的地址
            Ping ping = new Ping();
            PingReply reply = await ping.SendPingAsync(address);

            if (reply.Status == IPStatus.Success)
            {
                _logger.LogInformation($"Ping to {address} successful.");
                _logger.LogInformation($"Address: {reply.Address}");
                _logger.LogInformation($"Status: {reply.Status}");
            }
            else
            {
                _logger.LogInformation($"Ping to {address} failed. Status: {reply.Status}");
            }

            var url = $"{HUB.config.Text("hub协议")}://{HUB.config.Text("hub地址")}:{ServerInfo.Port}";
            _logger.LogInformation($"正在连接服务端 {url}");
            await socket.ConnectAsync(new Uri(url), cancellationToken);

            _logger.LogDebug("连接服务端成功");

            // 登录
            await socket.SendCmdAsync(MessageType.LogIn, logMsg, cancellationToken);
        }

        protected virtual string GetLoginMsg(CancellationToken cancellationToken)
        {
            var hubcfg = ExcelFile.Load(HUB.config.ExcelConfig.FullPath, "hub");
            var forwards = new List<ForwardConfig>();
            foreach (var hub in hubcfg.ReadAllLine())
            {
                if (string.IsNullOrEmpty(hub.Text("标识"))) continue;
                forwards.Add(new ForwardConfig
                {
                    LocalIp = hub.Text("内网"),
                    LocalPort = (int)hub.Number("内网端口"),
                    RemotePort = (int)hub.Number("服务端端口"),
                    Protocol = hub.Text("通讯协议").Equals("tcp", StringComparison.OrdinalIgnoreCase)? ProtocolEnum.TCP: ProtocolEnum.UDP
                });

            }

            return new LogInMassage
            {
                Forwards = forwards,
            }.ToJson(jsonTypeInfo: SourceGenerationContext.Default.LogInMassage);
        }

        protected async Task ReceiveServerAsync(CancellationToken cancellationToken)
        {
            var utility = new WebSocketUtility(socket, ProcessLine);
            await utility.ProcessLinesAsync(cancellationToken);
        }

        private void ProcessLine(ReadOnlySequence<byte> line, CancellationToken cancellationToken)
        {
            HandleServerRequestAsync(line, cancellationToken);
        }

        private void HandleServerRequestAsync(ReadOnlySequence<byte> line, CancellationToken cancellationToken)
        {
            try
            {
                var row = line.ToArray();
                var cmd = row[0];
                IClientHandler handler;
                switch ((MessageType)cmd)
                {
                    case MessageType.SwapMsg:
                    case MessageType.Forward:
                        handler = swapHandler;
                        break;
                    case MessageType.Log:
                        handler = logHandler;
                        break;
                    default:
                        throw new Exception($"未处理的消息：cmd={cmd}");
                }

                var content = Encoding.UTF8.GetString(line.Slice(1));
                handler.HandlerMsgAsync(this, content, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex);
            }
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("===== FastTunnel Client Stoping =====");
            if (socket != null)
            {
                socket.Abort();
            }
            await Task.CompletedTask;
        }
    }
}
